var signals = signals || {};

signals.signals = [];
signals.animLastUpdate = 0;
signals.pause = false;
signals.EventIDs = 0;

signals.animUpdate = function(timeStamp) {

	var timeSpan = timeStamp - signals.animLastUpdate;

	if (signals.animLastUpdate && !signals.pause) {

		if (timeSpan > 2000) {
			// time out
			console
				.log('signals.animUpdate() --> signal animation watchdog, skip animation step with a time span of '
					+ timeSpan + 'ms');
		} else if (timeSpan >= 500) {
			// each 500 ms scan signals
			var instances = [];
			signals.signals.foreach(function(i, value, first, last) {
				instances.push(value);
			});
			instances.foreach(function(i, value, first, last) {
				value.update(timeSpan / 1000);
			});
		} else
			return;
	}
	// remind time stamp
	signals.animLastUpdate = timeStamp;

};

signals.Signal = function(col, row, direction) {
	if (direction === undefined)
		return;
	fields.Field.call(this, col, row, 'signal');
	this.state = '';
	this.setDir(direction);
	this.track_field_id = undefined;
};
signals.Signal.prototype = new fields.Field();
signals.Signal.prototype.setDir = function(direction) {
	this.direction = Math.abs(direction) % 5;
	var angle = defs.SignalAngleByDirection[this.direction] || 0;
	var vec2a = new utils.Vec2A(this.vec2.x, this.vec2.y, angle);
	this.setInnerPos(vec2a);
};
signals.Signal.prototype.rotate = function(dir_inc) {
	var dir = Math.max(1, (this.direction + (dir_inc || 1)) % 5);
	this.setDir(dir);
};
signals.Signal.prototype.canPlace = function() {
	if (!this.placed()) {
		// check if there are other signals in this direction
		var sibl = this.getSiblings(this, 2);
		var thisDir = this.direction;
		var noOtherSignal = sibl.foreach(function(i, value, first, last) {
			if (value.direction === thisDir)
				return false;
			if (last)
				return true;
		}) || sibl.length === 0;
		if (!noOtherSignal)
			return false;

		// get rail track field
		var r = new fields.Rail(0, 0);
		var rails = this.getSiblings(r, 2);
		r.remove();
		if (rails.length === 0)
			return false;
		var f = rails[0];
		var dirMatch = f.directions.foreach(function(i, value, first, last) {
			if (value.indexOf(thisDir) >= 0)
				return true;
			if (last)
				return false;
		});
		if (dirMatch) {
			// store field id
			this.track_field_id = f.id;
			return true;
		}
	}
	return false;
};
signals.Signal.prototype.place = function() {
	this.track_field_id = undefined;
	fields.Field.prototype.place.call(this);
	if (this.track_field_id >= 0) {
		if (signals.signals.indexOf(this) < 0) {
			signals.signals.push(this);
		}
		return true;
	}
	return false;
};
signals.Signal.prototype.remove = function() {
	fields.Field.prototype.remove.call(this);
	signals.signals.remove(this);
};
signals.Signal.prototype.update = function(secs) {
	if (!this.placed()) {
		this.remove();
		return;
	}
	var newState = '';
	var track_field = fields.by_id[this.track_field_id];
	if (!track_field) {
		this.remove();
		return;
	}
	var next = track_field.getNext(this.direction);
	if (next) {
		var nxtFrom = defs.CounterDirection[this.direction];
		var nxtTo = next.getToDirs(nxtFrom);
		if (!nxtTo || nxtTo.length <= 0)
			newState = 'red';
		else {
			var canMove = nxtTo.foreach(function(i, value, first, last) {
				if (next.canMove(nxtFrom, value))
					return true;
				if (last)
					return false;
			});
			if (canMove)
				newState = 'green';
			else
				newState = 'red';
		}
	}
	if (this.state !== newState) {
		if (this.state !== '')
			$(this.el).removeClass(this.state);
		if (newState !== '')
			$(this.el).addClass(newState);
	}
	this.state = newState;
};

signals.Switch = function(col, row, direction) {
	if (direction === undefined)
		return;
	fields.Field.call(this, col, row, 'switch');
	this.setDir(direction);
	this.track_field_id = undefined;
	this.mov_fields_info = {};
	this.events = [];
};
signals.Switch.prototype = new fields.Field();
signals.Switch.prototype.setDir = function(direction) {
	this.direction = Math.abs(direction) % 5;
	var angle = defs.SignalAngleByDirection[this.direction] || 0;
	var vec2a = new utils.Vec2A(this.vec2.x, this.vec2.y, angle);
	this.setInnerPos(vec2a);
};
signals.Switch.prototype.rotate = function(dir_inc) {
	var dir = Math.max(1, (this.direction + (dir_inc || 1)) % 5);
	this.setDir(dir);
};
signals.Switch.prototype.canPlace = function() {
	if (!this.placed()) {
		// check if there are other signals in this direction
		var sibl = this.getSiblings(this);
		var thisDir = this.direction;
		var noOtherSignal = sibl.foreach(function(i, value, first, last) {
			if (value.direction === thisDir)
				return false;
			if (last)
				return true;
		}) || sibl.length === 0;
		if (!noOtherSignal)
			return false;

		// get rail track field
		var r = new fields.Rail(0, 0);
		var rails = this.getSiblings(r, 2);
		r.remove();
		if (rails.length === 0)
			return false;
		// store field id
		this.track_field_id = rails[0].id;
		return true;
	}
	return false;
};
signals.Switch.prototype.place = function() {
	this.track_field_id = undefined;
	fields.Field.prototype.place.call(this);
	if (this.track_field_id >= 0) {
		if (signals.signals.indexOf(this) < 0) {
			signals.signals.push(this);
		}
		/*
		 * initialize events
		 */
		// get field to track
		var track_field = fields.by_id[this.track_field_id];
		// get possible directions
		var dirSets = track_field.directions;
		var dirs = [];
		dirSets.foreach(function(i, value, fist, last) {
			dirs = dirs.concat(value);
		});
		// create an event for each direction
		var evts = [];
		dirs.foreach(function(i, value, first, last) {
			// entered events
			var evt = signals.Events.EVT_SWITCH_FIELD_ENTERED;
			var html = '<div class="event_config_entry"><div class="'
				+ evt.cls.join(' ') + '">' + evt.desc + '</div>'
				+ '<div class="event direction '
				+ defs.Direction.toString(value) + '">'
				+ defs.Direction.toString(value) + '</div> </div>';
			evts.push({
				event : evt,
				actions : [], // containing all related actions
				html : html, // html representation
				// internal filter parameter
				filter : {
					from : [ value ],
					to : undefined,
					left : false,
					entered : true,
					empty : undefined,
					cls : [ 'movable', 'train' ]
				}
			});
			// left events
			evt = signals.Events.EVT_SWITCH_FIELD_LEFT;
			html = '<div class="event_config_entry"><div class="'
				+ evt.cls.join(' ') + '">' + evt.desc + '</div>'
				+ '<div class="event direction '
				+ defs.Direction.toString(value) + '">'
				+ defs.Direction.toString(value) + '</div> </div>';
			evts.push({
				event : evt,
				actions : [], // containing all related actions
				html : html, // html representation
				// internal filter parameter
				filter : {
					from : undefined,
					to : [ value ],
					left : true,
					entered : false,
					empty : undefined,
					cls : [ 'movable', 'train' ]
				}
			});
			// empty events
			evt = signals.Events.EVT_SWITCH_FIELD_EMPTY;
			html = '<div class="event_config_entry"><div class="'
				+ evt.cls.join(' ') + '">' + evt.desc + '</div>'
				+ '<div class="event direction '
				+ defs.Direction.toString(value) + '">'
				+ defs.Direction.toString(value) + '</div> </div>';
			evts.push({
				event : evt,
				actions : [], // containing all related actions
				html : html, // html representation
				// internal filter parameter
				filter : {
					from : undefined,
					to : [ value ],
					left : undefined,
					entered : false,
					empty : true,
					cls : [ 'movable', 'train' ]
				}
			});
		});
		this.events = evts;
		return true;
	}
	return false;
};
signals.Switch.prototype.getEvents = function() {
	if (this.placed()) { return this.events; }
	return null;
};
signals.Switch.prototype.remove = function() {
	fields.Field.prototype.remove.call(this);
	signals.signals.remove(this);
};
signals.Switch.prototype.update = function(secs) {
	if (!this.placed()) {
		this.remove();
		return;
	}
	var track_field = fields.by_id[this.track_field_id];
	if (!track_field) {
		this.remove();
		return;
	}

	// get present move fields
	var mf = new movable.Movable(this.column, this.row, undefined, this.direction);
	var mov_fields = this.getSiblings(mf, 2);
	mf.remove();

	// scan for new fields
	var oldIds = this.mov_fields_info;
	var raised = [];
	var newMovFields = {};
	mov_fields
		.foreach(function(i, movField, first, last) {
			var movFrom = movField.speed >= 0 ? movField.fromDirection : movField.toDirection;
			var movTo = movField.speed >= 0 ? movField.toDirection : movField.fromDirection;
			// check if already here
			if (!(movField.id in oldIds)) {
				// new movable
				raised.push({
					id : movField.id,
					from : movFrom,
					to : movTo,
					left : false,
					entered : true,
					empty : (oldIds.length === 0 && newMovFields.length === 0),
					cls : movField.cls[1]
				});
			}
			// remind field
			newMovFields[movField.id] = {
				to : movTo,
				from : movFrom,
				cls : movField.cls
			};
		});
	// scan for left fields
	for ( var id in oldIds) {
		var value = oldIds[id];
		if (!(id in newMovFields)) {
			// left movable detected
			raised.push({
				id : id,
				from : value.from,
				to : value.to,
				left : true,
				entered : false,
				empty : (newMovFields.length === 0),
				cls : value.cls[1]
			});
		}
	}

	// remind
	this.mov_fields_info = newMovFields;

	// fire events
	var thisEvents = this.events;
	raised.foreach(function(i, raisedEvt, first, last) {

		// filter event
		thisEvents.foreach(function(evtI, evt, firstEvt, lastEvt) {
			if (evt.actions.length !== 0) {
				var match = true;
				for ( var f in evt.filter) {
					var fval = evt.filter[f];
					if (fval !== undefined) {
						console.log('check event vs. raised --> ' + f + ':' + fval + ' vs. ' + raisedEvt[f]);
						if (fval instanceof Array) {
							if (fval.indexOf(raisedEvt[f]) < 0) {
								match = false;
								break;
							}
						} else if (fval !== raisedEvt[f]) {
							match = false;
							break;
						}
					}
				}

				if (match) {
					console.log("match... fire " + evt.actions.length + " event(s)");
					// values matching - fire event
					evt.actions.foreach(function(actI, action, firstAct, lastAct) {
						var eventTarget = fields.by_id[action.field_id];
						console.log((actI+1) + '. (' + eventTarget.cls.join('/') + ').' + action.fn_name + '(' + action.args.join(', ') + ')');
						if (eventTarget) {
							fields.el.trigger(evt.event.on, [ action ]);
							console.log('fired');
						} else
							console.log('failed - event target not found');
					});
				} else
					console.log("dosn't match");
			}
		});
	});
};
signals.Switch.prototype.getActions = function() {
	var actions = [];
	var thisId = this.id;
	// move velo
	actions
		.push(new fields.Action(thisId, 'applyVeloMove', [ false ], '<div class="action_entry"><div class="action moveFwd">FWD</div></div>'));
	actions
		.push(new fields.Action(thisId, 'applyVeloMove', [ true ], '<div class="action_entry"><div class="action moveBwd">BWD</div></div>'));
	actions
		.push(new fields.Action(thisId, 'applyStop', [], '<div class="action_entry"><div class="action stop">STOP</div></div>'));
	this.actions = actions;
	return actions;
};
signals.Switch.prototype.applyVeloMove = function(reverse) {
	// get present move fields
	var mf = new movable.Movable(this.column, this.row, undefined, this.direction);
	var mov_fields = this.getSiblings(mf, 2);
	mf.remove();

	mov_fields
		.foreach(function(i, value, first, last) {
			value.move(reverse);
		});
};
signals.Switch.prototype.applyStop = function() {
	// get present move fields
	var mf = new movable.Movable(this.column, this.row, undefined, this.direction);
	var mov_fields = this.getSiblings(mf, 2);
	mf.remove();

	mov_fields.foreach(function(i, value, first, last) {
		value.stop();
	});
};

signals.Events = {
	EVT_SWITCH_FIELD_ENTERED : {
		on : 'action_receive',
		desc : 'entered from',
		cls : [ 'event', 'switch', 'entered' ]
	},
	EVT_SWITCH_FIELD_LEFT : {
		on : 'action_receive',
		desc : 'leave to',
		cls : [ 'event', 'switch', 'left' ]
	},
	EVT_SWITCH_FIELD_EMPTY : {
		on : 'action_receive',
		desc : 'empty',
		cls : [ 'event', 'switch', 'empty' ]
	}
};
